using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Collections;

namespace Decav.Collections.ObjectModel
{

    /// <summary>
    /// An object model collection that exposes events for when items are added, removed, or modified in the collection making it
    /// easier to update displays or take action.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Only when the list is modified through the this wrapper will the events be raised. That is, if
    /// the WatchableCollection is wrapping another IList object, then if items are added directly to the underlying IList, no events will
    /// be raised.
    /// </para>
    /// <para>
    /// There is a read-only wrapper for this collection which exposes a collection which cannot be modified externally. The
    /// <see cref="T:WatchableReadOnlyCollection`1"/> class can wrap this class for exposure to clients that are not allowed to
    /// directly modify the collection but still want to receive events.
    /// </para>
    /// </remarks>
    /// <typeparam name="T">The type of item that the collection contains.</typeparam>
    [Serializable]
    public class WatchableCollection<T> : Collection<T>
    {

        #region Field Declarations

        /// <summary>
        /// A read only wrapper for this collection if one has been generated.
        /// </summary>
        /// <remarks>
        /// Used by the implicit conversion so the same instance will always be returned.
        /// </remarks>
        private WatchableReadOnlyCollection<T> _wrapper;

        #endregion

        #region Event Declarations

        /// <summary>
        /// Event that is raised when an item is added to the collection.
        /// </summary>
        [Description( "Event that is raised when an item is added to the collection." )]
        public event ItemAddedEventHandler<T> Added;

        /// <summary>
        /// Event that is raised when an item is removed from the collection.
        /// </summary>
        [Description( "Event that is raised when an item is removed from the collection." )]
        public event ItemRemovedEventHandler<T> Removed;

        /// <summary>
        /// Event that is raised when an existing item in the list is modified.
        /// </summary>
        [Description( "Event that is raised when an existing item in the list is modified." )]
        public event ItemModifiedEventHandler<T> Modified;

        /// <summary>
        /// Event that is raised when the collection is cleared of all items.
        /// </summary>
        [Description( "Event that is raised when the collection is cleared of all items." )]
        public event EventHandler Cleared;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="T:WatchableCollection`1"/> class.
        /// </summary>
        /// <param name="list">The list that is wrapped by the new collection.</param>
        /// <exception cref="T:System.ArgumentNullException">list is null.</exception>
        public WatchableCollection( IList<T> list )
            : base( list )
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:WatchableCollection`1"/> class.
        /// </summary>
        public WatchableCollection( )
            : this( new List<T>( ) )
        {
        }

        #endregion

        #region Collection Methods

        /// <summary>
        /// Adds the elements of the specified collection to the end of the collection. 
        /// </summary>
        /// <param name="collection">The collection whose elements should be added to the end of the collection. The collection 
        /// itself cannot be <see langword="null" />, but it can contain elements that are <see langword="null" />, if type T is a 
        /// reference type</param>
        public void AddRange( IEnumerable<T> collection )
        {
            foreach ( T var in collection )
            {
                Add( var );
            }   // foreach
        }

        /// <summary>
        /// Wraps this collection with a read only wrapper.
        /// </summary>
        /// <returns>A read only wrapper around this collection. Multiple casts will yield the same wrapper.</returns>
        public WatchableReadOnlyCollection<T> AsReadOnly( )
        {
            return _wrapper ?? ( _wrapper = new WatchableReadOnlyCollection<T>( this ) );
        }

        /// <summary>
        /// Inserts an element into the <see cref="T:System.Collections.ObjectModel.Collection`1"></see> at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index at which item should be inserted.</param>
        /// <param name="item">The object to insert. The value can be null for reference types.</param>
        /// <exception cref="T:System.ArgumentOutOfRangeException">index is less than zero.-or-index is greater than <see cref="P:System.Collections.ObjectModel.Collection`1.Count"></see>.</exception>
        protected override void InsertItem( int index, T item )
        {

            // Let the base handle the real work
            base.InsertItem( index, item );

            // Raise the event
            OnAdded( new ItemAddedEventArgs<T>( index, item ) );

        }

        /// <summary>
        /// Removes the element at the specified index of the <see cref="T:System.Collections.ObjectModel.Collection`1"></see>.
        /// </summary>
        /// <param name="index">The zero-based index of the element to remove.</param>
        /// <exception cref="T:System.ArgumentOutOfRangeException">index is less than zero.-or-index is equal to or greater than <see cref="P:System.Collections.ObjectModel.Collection`1.Count"></see>.</exception>
        protected override void RemoveItem( int index )
        {

            // Get the item we're removing
            T removedItem = Items[index];

            // Let the base handle the real work
            base.RemoveItem( index );

            // Raise the event
            OnRemoved( new ItemRemovedEventArgs<T>( index, removedItem ) );

        }

        /// <summary>
        /// Replaces the element at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index of the element to replace.</param>
        /// <param name="item">The new value for the element at the specified index. The value can be null for reference types.</param>
        /// <exception cref="T:System.ArgumentOutOfRangeException">index is less than zero.-or-index is greater than <see cref="P:System.Collections.ObjectModel.Collection`1.Count"></see>.</exception>
        protected override void SetItem( int index, T item )
        {

            // Get the item we're replacing
            T replacedItem = Items[index];

            // Let the base handle the real work.
            base.SetItem( index, item );

            // Raise the event
            OnModified( new ItemModifiedEventArgs<T>( index, replacedItem, item ) );

        }

        /// <summary>
        /// Removes all elements from the <see cref="T:System.Collections.ObjectModel.Collection`1"></see>.
        /// </summary>
        protected override void ClearItems( )
        {

            // Let the base handle the real work
            base.ClearItems( );

            // Raise the event
            OnCleared( EventArgs.Empty );

        }

        #endregion

        #region Event Invoke Methods

        /// <summary>
        /// Raises the Added event to event subscribers.
        /// </summary>
        /// <remarks>
        /// Note to inheritors: You must call the base method in order to ensure that the event is raised and clean up
        /// work is properly performed.
        /// </remarks>
        /// <param name="e">Event arguments.</param>
        protected virtual void OnAdded( ItemAddedEventArgs<T> e )
        {

            // Invoke the event handlers
            if ( Added != null )
            {
                Added( this, e );
            }   // if

        }

        /// <summary>
        /// Raises the Removed event to event subscribers.
        /// </summary>
        /// <remarks>
        /// Note to inheritors: You must call the base method in order to ensure that the event is raised and clean up
        /// work is properly performed.
        /// </remarks>
        /// <param name="e">Event arguments.</param>
        protected virtual void OnRemoved( ItemRemovedEventArgs<T> e )
        {

            // Invoke the event handlers
            if ( Removed != null )
            {
                Removed( this, e );
            }   // if

        }

        /// <summary>
        /// Raises the Modified event to event subscribers.
        /// </summary>
        /// <remarks>
        /// Note to inheritors: You must call the base method in order to ensure that the event is raised and clean up
        /// work is properly performed.
        /// </remarks>
        /// <param name="e">Event arguments.</param>
        protected virtual void OnModified( ItemModifiedEventArgs<T> e )
        {

            // Invoke the event handlers
            if ( Modified != null )
            {
                Modified( this, e );
            }   // if

        }

        /// <summary>
        /// Raises the Cleared event to event subscribers.
        /// </summary>
        /// <remarks>
        /// Note to inheritors: You must call the base method in order to ensure that the event is raised and clean up
        /// work is properly performed.
        /// </remarks>
        /// <param name="e">Event arguments.</param>
        protected virtual void OnCleared( EventArgs e )
        {

            // Invoke the event handlers
            if ( Cleared != null )
            {
                Cleared( this, e );
            }   // if

        }

        #endregion

        #region Operators and Conversions

        /// <summary>
        /// Implicitly converts the specified collection to a read only wrapper.
        /// </summary>
        /// <param name="collection">The collection.</param>
        /// <returns>A read only wrapper around the specified collection. Multiple casts will yield the same wrapper.</returns>
        public static implicit operator WatchableReadOnlyCollection<T>( WatchableCollection<T> collection )
        {
            return collection == null ? null : collection.AsReadOnly( );
        }

        #endregion

    }

    #region Delegates

    /// <summary>
    /// Event handler delegate that is used to handle the <see cref="E:WatchableCollection`1.Added"/> event.
    /// </summary>
    /// <typeparam name="T">The type of the item that was added.</typeparam>
    /// <param name="sender">The collection that raised the event.</param>
    /// <param name="e">Event arguments.</param>
    public delegate void ItemAddedEventHandler<T>( object sender, ItemAddedEventArgs<T> e );

    /// <summary>
    /// Event handler delegate that is used to handle the <see cref="E:WatchableCollection`1.Removed"/> event.
    /// </summary>
    /// <typeparam name="T">The type of the item that was added.</typeparam>
    /// <param name="sender">The collection that raised the event.</param>
    /// <param name="e">Event arguments.</param>
    public delegate void ItemRemovedEventHandler<T>( object sender, ItemRemovedEventArgs<T> e );

    /// <summary>
    /// Event handler delegate that is used to handle the <see cref="E:WatchableCollection`1.Modified"/> event.
    /// </summary>
    /// <typeparam name="T">The type of the item that was added.</typeparam>
    /// <param name="sender">The collection that raised the event.</param>
    /// <param name="e">Event arguments.</param>
    public delegate void ItemModifiedEventHandler<T>( object sender, ItemModifiedEventArgs<T> e );

    #endregion

    #region Event Arguments

    /// <summary>
    /// Event arguments that are passed to the <see cref="T:WatchableCollection`1.Added" /> event.
    /// </summary>
    /// <typeparam name="T">The type of the item that was added.</typeparam>
    [Serializable]
    public class ItemAddedEventArgs<T> : EventArgs
    {

        private int _index;
        private T _item;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:ItemAddedEventArgs`1"/> class.
        /// </summary>
        /// <param name="item">The item that was added.</param>
        public ItemAddedEventArgs( T item )
            : this( -1, item )
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:ItemAddedEventArgs`1"/> class.
        /// </summary>
        /// <param name="index">The index of the item that was added or inserted.</param>
        /// <param name="item">The item that was added.</param>
        public ItemAddedEventArgs( int index, T item )
        {

            if ( index < -1 )
                throw new ArgumentOutOfRangeException( "index", index, "Index must be zero or more, or -1 to indicate an unknown or irrelevant index." );

            _index = index;
            _item = item;

        }

        /// <summary>
        /// Gets the index where the insert or add occurred or -1 if unknown.
        /// </summary>
        public int Index
        {
            get
            {
                return _index;
            }
        }

        /// <summary>
        /// Gets the item that was added to the collection.
        /// </summary>
        public T NewItem
        {
            get
            {
                return _item;
            }
        }

    }   // class

    /// <summary>
    /// Event arguments that are passed to the <see cref="E:WatchableCollection`1.Removed"/> event.
    /// </summary>
    /// <typeparam name="T">The type of the item that was removed.</typeparam>
    [Serializable]
    public class ItemRemovedEventArgs<T> : EventArgs
    {

        private int _index;
        private T _item;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:ItemRemovedEventArgs`1"/> class.
        /// </summary>
        /// <param name="item">The item that was removed.</param>
        public ItemRemovedEventArgs( T item )
            : this( -1, item )
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:ItemRemovedEventArgs`1"/> class.
        /// </summary>
        /// <param name="index">The index of the item that was removed.</param>
        /// <param name="item">The item that was removed.</param>
        public ItemRemovedEventArgs( int index, T item )
        {

            if ( index < -1 )
                throw new ArgumentOutOfRangeException( "index", index, "Index must be zero or more, or -1 to indicate an unknown or irrelevant index." );

            _index = index;
            _item = item;

        }

        /// <summary>
        /// Gets the index where the remove occurred or -1 if unknown.
        /// </summary>
        public int Index
        {
            get
            {
                return _index;
            }
        }

        /// <summary>
        /// Gets the item that was removed to the collection.
        /// </summary>
        public T RemovedItem
        {
            get
            {
                return _item;
            }
        }

    }   // class

    /// <summary>
    /// Event arguments that are passed to the <see cref="E:WatchableCollection`1.Modified"/> event.
    /// </summary>
    /// <typeparam name="T">The type of the item that was modified.</typeparam>
    [Serializable]
    public class ItemModifiedEventArgs<T> : EventArgs
    {

        private int _index;
        private T _oldItem;
        private T _newItem;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:ItemModifiedEventArgs`1"/> class.
        /// </summary>
        /// <param name="index">The index of the item being replaced.</param>
        /// <param name="oldItem">The old item.</param>
        /// <param name="newItem">The new item.</param>
        public ItemModifiedEventArgs( int index, T oldItem, T newItem )
        {

            if ( index < -1 )
                throw new ArgumentOutOfRangeException( "index", index, "Index must be zero or more, or -1 to indicate an unknown or irrelevant index." );

            _index = index;
            _oldItem = oldItem;
            _newItem = newItem;

        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:ItemModifiedEventArgs`1"/> class.
        /// </summary>
        /// <param name="oldItem">The old item.</param>
        /// <param name="newItem">The new item.</param>
        public ItemModifiedEventArgs( T oldItem, T newItem )
            : this( -1, oldItem, newItem )
        {
        }

        /// <summary>
        /// Gets the index where the remove occurred or -1 if unknown.
        /// </summary>
        public int Index
        {
            get
            {
                return _index;
            }
        }

        /// <summary>
        /// Gets the old item that is being replaced.
        /// </summary>
        public T OldItem
        {
            get
            {
                return _oldItem;
            }
        }

        /// <summary>
        /// Gets the new item that is replacing <see cref="OldItem"/> in the collection.
        /// </summary>
        public T NewItem
        {
            get
            {
                return _newItem;
            }
        }

    }   // class

    #endregion

}   // namespace
